// 创建型设计模式
/**
 * 工厂模式
 * 只暴露出一个工厂方法，实际上建造什么样的实际对象，我们并不关心
 */
// 暴露出一个工厂类，或者你也写成构造函数也行，比如说这个工厂类叫User，构造不一样的角色，不同的角色的有不一样的属性
class User {
  //构造器
  constructor(opt) {
    this.viewPage = opt.viewPage;
    this.name = opt.name;
  }

  //静态方法，这是内部实现工厂方法的细节
  static getInstance(role) {
    switch (role) {
      case 'superAdmin':
        return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] });
        break;
      case 'admin':
        return new User({ name: '管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据'] });
        break;
      case 'user':
        return new User({ name: '普通用户', viewPage: ['首页', '通讯录', '发现页'] });
        break;
      default:
        throw new Error('参数错误, 可选参数:superAdmin、admin、user')
    }
  }
}

//调用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');

/**
 * 单例模式，保证一个类仅有一个实例
 */
class Singleton {
  constructor () {}
}

Singleton.getInstance = (function () {
  let instance = null
  return function () {
    if (!instance) {
      instance = new Singleton()
    }
    return instance
  }
})()

let s1 = Singleton.getInstance()
let s2 = Singleton.getInstance()
console.log(s1 === s2)  //  true

// 结构型设计模式
/**
 * 适配器模式
 * 将一个接口转换成那个自己希望的另一个接口
 */
class Plug {
  getName () {
    return '港版插头'
  }
}

class Target {
  constructor () {
    this.plug = new Plug()
  }
  getName () {
    return this.plug.getName() + ' 适配器转大陆插头'
  }
}

const plug = new Target()
console.log(plug.getName())

/**
 * 装饰器模式
 * 在不改变原对象的基础上，通过对其进行包装拓展（添加属性或方法）使
 * 原有对象可以满足用户更复杂的需求
 */
let xiaoCai = {
  sing () {
    console.log('我会打篮球')
  }
}

let xiaoLi = {
  sing () {
    console.log('我也想学打篮球')
  }
}

const xiaoLiSing = xiaoLi.sing
xiaoLi.sing = () => {
  xiaoLiSing()
  xiaoCai.sing()
}

// 行为型设计模式
/**
 * 发布-订阅模式
 * 也叫 观察者模式
 * 通过一对一或者一对多的依赖关系，当对象发生改变时，订阅方都会收到通知。
 */
const event = {
  eventList: [],
  listen (key, fn) {
    if ('function' !== typeof fn) {
      console.log('请添加回调函数')
      return
    }
    if (!this.eventList[key]) {
      this.eventList[key] = []
    }
    this.eventList[key].push(fn)
  },
  trigger (key, ...args) {
    const fns = this.eventList[key]
    if (!fns || !fns.length) {
      return false
    }
    fns.forEach(fn => {
      fn.apply(this, args)
    })
  }
}
event.listen('click', function () { console.log('添加订阅事件1') })
event.listen('click', function () { console.log('添加订阅事件2') })
event.trigger('click')
